#include "config.h"

#include <sys/types.h>
#include <sys/time.h>
#include <sys/stat.h>
#include <sys/resource.h>
#include <sys/prctl.h>
#include <sys/ioctl.h>
#include <sys/file.h>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>
#include <locale.h>
#include <fcntl.h>
#include <errno.h>
#include <time.h>
#include <sys/types.h>
#include <sys/wait.h>

#define DBG_SUBSYS S_LIBYLIB

#include "configure.h"
#include "sysutil.h"
#include "sysy_lib.h"
#include "dbg.h"
#include "ylog.h"

int nofile_cur;
int nofile_max;

static int __preserved(int fd, const int *fds, int count)
{
        int i;

        for (i = 0; i < count; i++) {
                if (fds[i] == fd)
                        return 1;
        }

        return 0;
}

int daemonlize(int daemon, int maxcore, char *chr_path, int *preservefds, int count)
{
        int ret, fd, i;
        int64_t maxopenfile;
        struct rlimit rlim, rlim_new;

        maxopenfile = (1024 * 10);

        maxcore = gloconf.maxcore;

        if (maxcore) {
                /*
                 * first try raising to infinity; if that fails, try bringing
                 * the soft limit to the hard.
                 */
                ret = getrlimit(RLIMIT_CORE, &rlim);
                if (ret == 0) {
                        rlim_new.rlim_cur = rlim_new.rlim_max = RLIM_INFINITY;

                        ret = setrlimit(RLIMIT_CORE, &rlim_new);
                        if (ret == -1) {
                                ret = errno;
                                DWARN("%d - %s\n", ret, strerror(ret));

                                /* failed. try raising just to the old max */
                                rlim_new.rlim_cur = rlim_new.rlim_max
                                                  = rlim.rlim_max;

                                ret = setrlimit(RLIMIT_CORE, &rlim_new);
                                if (ret == -1) {
                                        ret = errno;
                                        DWARN("%d - %s\n", ret, strerror(ret));
                                }
                        }
                } else {        /* -1 */
                        ret = errno;
                        DWARN("%d - %s\n", ret, strerror(ret));
                }

                /*
                 * getrlimit again to see what we ended up with. only fail if
                 * the soft limit ends up 0, because then no core files will
                 * be created at all.
                 */

                 ret = getrlimit(RLIMIT_CORE, &rlim);
                 if (ret == -1 || rlim.rlim_cur == 0) {
                        if (ret == -1)
                                ret = errno;
                        else
                                ret = EPERM;

                        GOTO(err_ret, ret);
                 }
        }

        if (!gloconf.testing) {
                rlim_new.rlim_cur = maxopenfile;
                rlim_new.rlim_max = maxopenfile;  /* XXX max open file num  */
                ret = setrlimit(RLIMIT_NOFILE, &rlim_new);
                if (ret == -1) {
                        ret = errno;
                        if (ret == EPERM) {
                                if (daemon)
                                        GOTO(err_ret, ret);

                                //DWARN("please set /etc/security/limits.conf nofile %llu\n", (LLU)maxopenfile);
                        } else
                                GOTO(err_ret, ret);
                        //DWARN("%d - %s\n", ret, strerror(ret));
                }
        }

        ret = getrlimit(RLIMIT_NOFILE, &rlim_new);
        if (ret == -1) {
                ret = errno;
                DWARN("%d - %s\n", ret, strerror(ret));
        }

        nofile_cur = rlim_new.rlim_cur;
        nofile_max = rlim_new.rlim_max;

        DBUG("max %llu\n", (LLU)rlim_new.rlim_max);

        srandom(getpid());

        /* set stderr non-buffering (for running under, say, daemontools) */
        setbuf(stderr, NULL);

        /* read zone info now, in case we chroot() */
        tzset();

        /* for nice %b handling in strfime() */
        setlocale(LC_TIME, "C");

        /* enable core dump */
        prctl(PR_SET_DUMPABLE, 1, 0, 0, 0);

        /* set signal handler */
        signal(SIGPIPE, SIG_IGN);

        /* set Umask */
        (void) umask(022);

#if 1
        if (daemon) {
                /* set signal handler */
                //if (signal(SIGTERM, termination_handler) == SIG_IGN)
		signal(SIGTERM, SIG_IGN);
                signal(SIGHUP, SIG_IGN);
                signal(SIGINT, SIG_IGN);

                /* operate in background */
                ret = fork();
                if (ret == -1) {
                        ret = errno;
                        GOTO(err_ret, ret);
                } else if (ret > 0)          /* parent, exit */
                        exit(1);

                /*
                 * be the process group leader
                 * even if we don't daemonize, we still want to disown our
                 * parent process
                 */
                ret = setsid();
                if (ret == -1) {
                        ret = errno;
                        GOTO(err_ret, ret);
                }
        }
#endif

        /* close inherited descriptors */
        if (daemon) {
                for (i = getdtablesize() - 1; i >= 0; --i) {
                        if (preservefds && !__preserved(i,  preservefds, count))
                                sy_close_failok(i);
                }
        }

        /* open standard descriptors */
        fd = open("/dev/null", O_RDWR);        /* stdin */
        if (fd == -1) {
                ret = errno;
                GOTO(err_ret, ret);
        }
        ret = dup(fd);                        /* stdout */
        ret = dup(fd);                        /* stderr */

        /*
         * detach from controlling terminal
         * and set process group if needed
         */
        fd = open("/dev/tty", O_RDWR);
        if (fd != -1) {
                if (getpgrp() != getpid()) {
                        (void) ioctl(fd, TIOCNOTTY, 0);
                        (void) setpgid(0, getpid());
                }
                (void) sy_close(fd);
        }

        /* move to a safe and known directory */
        if (chr_path)
                ret = chdir(chr_path);
        else
                ret = chdir("/");

        return 0;
err_ret:
        return ret;
}

int daemon_pid(const char *path)
{
        int ret, fd;
        char pid[128];

        ret = path_validate(path, YLIB_NOTDIR, YLIB_DIRCREATE);
        if (unlikely(ret))
                GOTO(err_ret, ret);

        /* record process ID */
        fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0640);
        if (fd == -1) {
                ret = errno;
                GOTO(err_ret, ret);
        }

        snprintf(pid, 128, "%d\n", getpid());

        DBUG("get pid %s\n", pid);

        ret = _write(fd, pid, _strlen(pid));
        if (ret < 0) {
                ret = -ret;
                GOTO(err_fd, ret);
        }

        close(fd);

        return 0;
err_fd:
        close(fd);
err_ret:
        return ret;
}

int daemon_lock(const char *key)
{
        int ret, fd, retry = 0, flags;

        ret = path_validate(key, YLIB_NOTDIR, YLIB_DIRCREATE);
        if (unlikely(ret))
                GOTO(err_ret, ret);

        /* avoid multiple copies */
        fd = open(key, O_CREAT | O_RDONLY, 0640);
        if (fd == -1) {
                ret = errno;
                GOTO(err_ret, ret);
        }

        flags = fcntl(fd, F_GETFL, 0);
        if (flags < 0 ) {
                ret = errno;
                GOTO(err_fd, ret);
        }

        ret = fcntl(fd, F_SETFL, flags | FD_CLOEXEC);
        if (ret < 0) {
                ret = errno;
                GOTO(err_fd, ret);
        }

retry:
        ret = flock(fd, LOCK_EX | LOCK_NB);
        if (ret == -1) {
                ret = errno;
                if (ret == EWOULDBLOCK) {
                        if (retry < 20) {
                                DINFO("lock %s fail\n", key);
                                sleep(1);
                                retry++;
                                goto retry;
                        } else {
                                ret = EBUSY;
                                DINFO("lock %s fail\n", key);
                                GOTO(err_fd, ret);
                        }
                } else
                        GOTO(err_fd, ret);
        }

        return fd;
err_fd:
        close(fd);
err_ret:
        return -ret;
}

int daemon_update(const char *key, const char *value)
{
        int ret, fd;
        char path[MAX_PATH_LEN];

        snprintf(path, MAX_PATH_LEN, "%s", key);

        ret = path_validate(path, YLIB_NOTDIR, YLIB_DIRCREATE);
        if (unlikely(ret))
                GOTO(err_ret, ret);

        fd = open(key, O_RDWR, 0640);
        if (fd == -1) {
                ret = errno;
                GOTO(err_ret, ret);
        }

        ret = flock(fd, LOCK_SH | LOCK_NB);
        if (ret == -1) {
                ret = errno;
                if (ret == EWOULDBLOCK) {
                        DBUG("lock complete\n");
                } else
                        GOTO(err_fd, ret);
        } else {
                YASSERT(0);
        }

        ret = ftruncate(fd, 0);
        if (unlikely(ret))
                GOTO(err_fd, ret);

        ret = _write(fd, value, strlen(value));
        if (ret < 0) {
                ret = -ret;
                GOTO(err_fd, ret);
        }

        close(fd);

        return 0;
err_fd:
        close(fd);
err_ret:
        return ret;
}
